package com.xh3140.xwidget.dialog;

import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.ColorStateList;
import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;

import androidx.annotation.ColorInt;
import androidx.annotation.FloatRange;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.annotation.Size;
import androidx.annotation.StyleRes;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModelProvider;

import com.xh3140.xwidget.dialog.callback.ConfigParamsCallback;
import com.xh3140.xwidget.dialog.params.DialogParams;
import com.xh3140.xwidget.dialog.view.FooterView;
import com.xh3140.xwidget.dialog.view.RootView;

/**
 * 抽象对话框类
 */
@SuppressWarnings("unused")
public abstract class AbstractDialog<V extends View> extends DialogFragment {
  protected DialogParams mDialogParams;
  private RootView<V> mRootView;

  /**
   * 获取根布局视图
   * 注意在{@code onViewCreated()}之前，返回{@code null}
   */
  @Nullable
  public RootView<V> getRootView() {
    return mRootView;
  }

  @NonNull
  public RootView<V> requireRootView() {
    final RootView<V> rootView = getRootView();
    if (rootView == null) {
      throw new NullPointerException("AbstractDialog " + this + " does not have a RootView<?>.");
    }
    return rootView;
  }

  /**
   * 创建根视图，它包含所有的显示部件
   *
   * @param context 上下文
   */
  protected abstract RootView<V> createRootView(@NonNull Context context);

  @Override
  public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    final RootView<V> rootView = createRootView(requireContext());
    final GradientDrawable divider = new GradientDrawable();
    divider.setColor(ColorStateList.valueOf(mDialogParams.dividerColor));
    divider.setSize(mDialogParams.dividerThickness, mDialogParams.dividerThickness);
    divider.setAlpha(mDialogParams.alpha);
    rootView.setShowDividers(mDialogParams.showDividers);
    rootView.setDividerDrawable(divider);
    final FooterView footerView = rootView.getFooterView();
    if (footerView != null) {
      footerView.setDividerDrawable(rootView.getDividerDrawable());
    }
    mRootView = rootView;
    return rootView;
  }

  @Override
  public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    // 设置对话框风格，无标题，无边框
    setStyle(STYLE_NO_TITLE, 0);
  }

  @Override
  public void onStart() {
    final Dialog dialog = requireDialog();
    // 调整对话框高度
    final View view = requireView();
    view.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
      final int minHeight, maxHeight;
      if (mDialogParams.minHeight > 1f) {
        minHeight = (int) mDialogParams.minHeight;
      } else {
        minHeight = (int) (mDialogParams.minHeight * getResources().getDisplayMetrics().heightPixels);
      }
      if (mDialogParams.maxHeight > 1f) {
        maxHeight = (int) mDialogParams.maxHeight;
      } else {
        maxHeight = (int) (mDialogParams.maxHeight * getResources().getDisplayMetrics().heightPixels);
      }
      final int height = view.getHeight();
      if (height < minHeight) {
        view.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, minHeight));
      }
      if (height > maxHeight) {
        view.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, maxHeight));
      }
    });
    // 设置对话框外部触摸关闭
    dialog.setCanceledOnTouchOutside(mDialogParams.canceledOnTouchOutside);
    // 设置对话框返回键关闭
    dialog.setCancelable(mDialogParams.cancelable);
    // 设置窗口属性，当如果Activity是可见的
    final Window window = dialog.getWindow();
    if (window != null) {
      // 设置对话框背景
      final GradientDrawable background = new GradientDrawable();
      background.setColor(ColorStateList.valueOf(mDialogParams.backgroundColor));
      background.setCornerRadius(mDialogParams.radius);
      background.setAlpha(mDialogParams.alpha);
      window.setBackgroundDrawable(background);
      // 背景是否昏暗
      if (mDialogParams.dimEnabled) {
        window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
      } else {
        window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
      }
      // 设置对话框位置
      final WindowManager.LayoutParams params = window.getAttributes();
      params.gravity = mDialogParams.gravity;
      params.x = mDialogParams.offsetX;
      params.y = mDialogParams.offsetY;
      // 设置对话框外边距
      final int[] margins = mDialogParams.margins;
      if (margins != null) {
        params.width = WindowManager.LayoutParams.MATCH_PARENT;
        // 对话框的外边距是用DecorView的外边距来模拟的
        window.getDecorView().setPaddingRelative(margins[0], margins[1], margins[2], margins[3]);
      } else {
        // 设置宽度
        if (mDialogParams.width > 1f) {
          params.width = (int) mDialogParams.width;
        } else {
          params.width = (int) (mDialogParams.width * getResources().getDisplayMetrics().widthPixels);
        }
      }
      window.setAttributes(params);
      // 进场动画和退场动画
      if (mDialogParams.windowAnimations == 0) {
        switch (params.gravity) {
          case Gravity.CENTER:
            window.setWindowAnimations(R.style.animation_dialog_center);
            break;
          case Gravity.TOP:
            window.setWindowAnimations(R.style.animation_dialog_top);
            break;
          case Gravity.BOTTOM:
            window.setWindowAnimations(R.style.animation_dialog_bottom);
            break;
          default:
            break;
        }
      } else {
        window.setWindowAnimations(mDialogParams.windowAnimations);
      }
    }
    super.onStart();
  }

  @Override
  public void onDismiss(@NonNull DialogInterface dialog) {
    super.onDismiss(dialog);
    // 删除对话框Fragment
    final FragmentTransaction transaction = getParentFragmentManager().beginTransaction();
    transaction.remove(this);
    transaction.addToBackStack(null);
  }

  @Override
  public void show(@NonNull FragmentManager manager, @Nullable String tag) {
    final FragmentTransaction transaction = manager.beginTransaction();
    if (isAdded()) {
      transaction.remove(this).commit();
    }
    // 添加对话框Fragment
    transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
    transaction.add(this, tag);
    transaction.commitAllowingStateLoss();
  }

  /**
   * 重载显示
   *
   * @param tag 标签
   * @see DialogParams#DialogParams(FragmentActivity)
   * @see DialogParams#DialogParams(Fragment)
   */
  public void show(@Nullable String tag) {
    if (mDialogParams.manager == null) {
      throw new NullPointerException("FragmentManager is null.");
    }
    show(mDialogParams.manager, tag);
  }

  /**
   * 重载显示
   *
   * @see DialogParams#DialogParams(FragmentActivity)
   * @see DialogParams#DialogParams(Fragment)
   */
  public void show() {
    show(null);
  }

  /**
   * 弹出输入法
   */
  protected void showSoftInput() {
    final Window window = requireDialog().getWindow();
    if (window != null) {
      window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
    }
  }

  /**
   * 设置位置
   */
  protected void setLocation(int x, int y) {
    final Window window = requireDialog().getWindow();
    if (window != null) {
      final WindowManager.LayoutParams params = window.getAttributes();
      mDialogParams.offsetX = x;
      params.x = mDialogParams.offsetX;
      mDialogParams.offsetY = y;
      params.y = mDialogParams.offsetY;
      window.setAttributes(params);
    }
  }

  /**
   * 重置大小
   */
  protected void resize() {
    final Window window = requireDialog().getWindow();
    if (window != null) {
      final WindowManager.LayoutParams params = window.getAttributes();
      final int minimum = getResources().getDisplayMetrics().widthPixels / 3;
      if (mDialogParams.width < minimum) {
        params.width = minimum;
      } else {
        params.width = (int) mDialogParams.width;
      }
      window.setAttributes(params);
    }
  }

  /**
   * 数据状态保存
   * 由于屏幕方向旋转等情况会导致构建数据丢失，所以需要保存
   */
  protected static abstract class ViewModel extends androidx.lifecycle.ViewModel {
    private final MutableLiveData<DialogParams> mDialogParamsData = new MutableLiveData<>();

    protected void configAbstractDialogParams(@NonNull final AbstractDialog<?> dialog) {
      if (dialog.mDialogParams == null) {
        dialog.mDialogParams = mDialogParamsData.getValue();
      } else {
        mDialogParamsData.postValue(dialog.mDialogParams);
      }
    }

    @NonNull
    public static <VM extends ViewModel> VM getDefault(@NonNull final AbstractDialog<?> owner, @NonNull Class<VM> vmClass) {
      return new ViewModelProvider(owner.getViewModelStore(), owner.getDefaultViewModelProviderFactory()).get(vmClass);
    }
  }

  /**
   * 构建器
   * 提供对话框属性的配置
   */
  public static abstract class Builder<D extends AbstractDialog<?>, B extends Builder<D, B>> {
    /**
     * 对话框属性
     */
    protected final DialogParams mDialogParams;

    public Builder(@NonNull Context context) {
      mDialogParams = new DialogParams(context);
    }

    public Builder(@NonNull FragmentActivity activity) {
      mDialogParams = new DialogParams(activity);
    }

    public Builder(@NonNull Fragment fragment) {
      mDialogParams = new DialogParams(fragment);
    }

    /**
     * 转换为继承可用链式编程构建器
     *
     * @return 可以链式编程的构建器
     */
    @SuppressWarnings("unchecked")
    private B toBuilder() {
      return (B) this;
    }

    /**
     * 构建对话框
     * 这个方法应该在所有配置结束后调用
     */
    public abstract D build();

    /**
     * 配置对话框属性
     *
     * @param callback 配置回调
     */
    public B configDialogParams(@NonNull ConfigParamsCallback<DialogParams> callback) {
      callback.callback(mDialogParams);
      return toBuilder();
    }

    /**
     * 设置对话框位置
     * 这个位置是相对于应用窗口的
     *
     * @param gravity 位置
     */
    public B setDialogGravity(int gravity) {
      mDialogParams.gravity = gravity;
      return toBuilder();
    }

    /**
     * 设置对话框背景是否昏暗
     * true 对话框背景昏暗
     * false 对话框背景不昏暗
     *
     * @param enabled 标识
     */
    public B setDialogDimEnabled(boolean enabled) {
      mDialogParams.dimEnabled = enabled;
      return toBuilder();
    }

    /**
     * 设置对话框返回键是否关闭
     * true 表示按下应用返回键时对话框会触发关闭
     * false 表示按下应用返回键时对话框不会触发关闭
     *
     * @param cancelable 是否关闭
     */
    public B setDialogCanceledBack(boolean cancelable) {
      mDialogParams.cancelable = cancelable;
      return toBuilder();
    }

    /**
     * 设置对话框是否点击外部关闭
     * true 表示点击对话框外部时对话框会触发关闭
     * false 表示点击对话框外部时对话框不会触发关闭
     *
     * @param canceled 是否关闭
     */
    public B setDialogCanceledOnTouchOutside(boolean canceled) {
      mDialogParams.canceledOnTouchOutside = canceled;
      return toBuilder();
    }

    /**
     * 设置对话框透明度，范围：0-255；255不透明
     * 范围：0 - 255
     * 值越小表示越透明
     * 0x00 完全透明
     * 0xFF 完全不透明
     *
     * @param alpha 透明度百分比
     */
    public B setDialogAlpha(@IntRange(from = 0x00, to = 0xFF) int alpha) {
      mDialogParams.alpha = alpha;
      return toBuilder();
    }

    /**
     * 设置对话框透明度，范围：0-1；1不透明
     * 范围：0.0F - 1.0F
     * 值越小表示对话框越透明
     * 0.0F 对话框完全透明
     * 1.0F 对话框完全不透明
     *
     * @param alpha 透明度百分比
     */
    public B setDialogAlpha(@FloatRange(from = 0.0, to = 1.0) float alpha) {
      mDialogParams.alpha = (int) (0xFF * alpha);
      return toBuilder();
    }

    /**
     * 设置对话框宽度
     * 在0-1范围内：表示的是宽度相对窗口宽度所占的百分比
     * 大于1时：表示宽度的真实像素值
     *
     * @param width 宽度
     */
    public B setDialogWidth(float width) {
      mDialogParams.width = width;
      return toBuilder();
    }

    /**
     * 设置对话框最大高度
     * 在0-1范围内：表示的是最大高度相对窗口高度所占的百分比
     * 大于1时：表示最大高度的真实像素值
     * 当的实际高度大于这个高度时，会被限制在这个高度值
     *
     * @param maxHeight 最大高度
     */
    public B setDialogMaxHeight(float maxHeight) {
      mDialogParams.maxHeight = maxHeight;
      return toBuilder();
    }

    /**
     * 设置对话框窗口动画
     *
     * @param resId 动画资源ID
     */
    public B setDialogWindowAnimations(@StyleRes int resId) {
      mDialogParams.windowAnimations = resId;
      return toBuilder();
    }

    /**
     * 设置对话框背景颜色 argb
     *
     * @param color 背景颜色
     */
    public B setDialogBackgroundColor(@ColorInt int color) {
      mDialogParams.backgroundColor = color;
      return toBuilder();
    }

    /**
     * 设置对话框分割线显示方式
     * 如果这个值小于0，分割线只会在适当的时候显示在按钮上方
     *
     * @param showDividers 分割线显示方式
     */
    public B setDialogShowDividers(int showDividers) {
      mDialogParams.showDividers = showDividers;
      return toBuilder();
    }

    /**
     * 设置对话框分割线颜色 argb
     *
     * @param color 分割线颜色
     */
    public B setDialogDividerColor(@ColorInt int color) {
      mDialogParams.dividerColor = color;
      return toBuilder();
    }

    /**
     * 设置对话框分割线厚度 px
     *
     * @param thickness 分割线厚度
     */
    public B setDialogDividerThickness(@Px int thickness) {
      mDialogParams.dividerThickness = thickness;
      return toBuilder();
    }

    /**
     * 设置对话框外边距
     * 格式：[start, top, end, bottom] px
     *
     * @param margins 外边距
     */
    public B setDialogMargins(@Nullable @Size(4) int[] margins) {
      mDialogParams.margins = margins;
      return toBuilder();
    }

    /**
     * 设置对话框外边距
     *
     * @param margins 外边距 px
     */
    public B setDialogMargins(@Px int margins) {
      return setDialogMargins(new int[]{margins, margins, margins, margins});
    }

    /**
     * 设置对话框外边距
     *
     * @param left   左边距 px
     * @param top    上边距 px
     * @param right  右边距 px
     * @param bottom 下边距 px
     */
    public B setDialogMargins(@Px int left, @Px int top, @Px int right, @Px int bottom) {
      return setDialogMargins(new int[]{left, top, right, bottom});
    }

    /**
     * 设置对话框的圆角半径 px
     *
     * @param radius 圆角半径
     */
    public B setDialogRadius(int radius) {
      mDialogParams.radius = radius;
      return toBuilder();
    }

    /**
     * 设置对话框X坐标偏移 px
     *
     * @param x X坐标
     */
    public B setDialogX(int x) {
      mDialogParams.offsetX = x;
      return toBuilder();
    }

    /**
     * 设置对话框Y坐标偏移 px
     *
     * @param y Y坐标
     */
    public B setDialogY(int y) {
      mDialogParams.offsetY = y;
      return toBuilder();
    }

    /**
     * 设置对话框Fragment管理者
     *
     * @param manager Fragment管理者
     */
    public B setDialogFragmentManager(@Nullable FragmentManager manager) {
      mDialogParams.manager = manager;
      return toBuilder();
    }
  }
}
